package de.h_da.fbi.cg.pointcloud.ply;

import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
import java.util.Scanner;
import java.util.regex.MatchResult;

import org.anddev.progressmonitor.ProgressMonitor;

import de.h_da.fbi.cg.pointcloud.util.StreamUtils;

/**
 * @author Nicolas Gramlich
 * @since 17:15:57 - 25.10.2010
 */
public class SimplePLYParser {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final String PLY_MAGIC_STRING = "ply";
	private static final String PLY_FORMAT_ASCII_10 = "format ascii 1.0";

	private static final String PLY_ELEMENT_VERTEX_PATTERN = "element vertex ([0-9]+)";
	private static final String PLY_PROPERTY_FLOAT_X = "property float x";
	private static final String PLY_PROPERTY_FLOAT_Y = "property float y";
	private static final String PLY_PROPERTY_FLOAT_Z = "property float z";

	private static final String PLY_END_HEADER = "end_header";

	// ===========================================================
	// Fields
	// ===========================================================

	// ===========================================================
	// Constructors
	// ===========================================================

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	// ===========================================================
	// Methods
	// ===========================================================

	public static float[] parseAscii(final InputStream pInputStream, final ProgressMonitor pProgressMonitor) throws IOException {
		pProgressMonitor.onProgressChanged(0);
		
		final Scanner scanner = new Scanner(pInputStream).useLocale(Locale.US);
		try {
			SimplePLYParser.verifyAsciiFileTypeOrThrow(scanner);

			final int vertexCount = SimplePLYParser.extractVertexCountOrThrow(scanner);
			

			SimplePLYParser.verifyVertexProperties(scanner);
			SimplePLYParser.seekEndOfHeader(scanner);

			final int floatsToRead = vertexCount * 3;
			
			final float[] out = new float[floatsToRead];

			for(int i = 0; i < floatsToRead; /* Loop-internal-incrementation. */) {
				/* Read x/y/z of each vertex. */
				out[i++] = scanner.nextFloat();
				out[i++] = scanner.nextFloat();
				out[i++] = scanner.nextFloat();

				if(i % 300 == 0) {
					pProgressMonitor.onProgressChanged((int)((float)i * 100 / floatsToRead));
				}
			}
			pProgressMonitor.onProgressChanged(100);

			return out;
		} finally {
			StreamUtils.closeStream(pInputStream);
		}
	}

	private static void seekEndOfHeader(final Scanner pScanner) {
		SimplePLYParser.readSpecificLineOrThrow(pScanner, PLY_END_HEADER);
	}

	private static void verifyVertexProperties(final Scanner pScanner) {
		SimplePLYParser.readSpecificLineOrThrow(pScanner, PLY_PROPERTY_FLOAT_X);
		SimplePLYParser.readSpecificLineOrThrow(pScanner, PLY_PROPERTY_FLOAT_Y);
		SimplePLYParser.readSpecificLineOrThrow(pScanner, PLY_PROPERTY_FLOAT_Z);
	}

	private static void readSpecificLineOrThrow(final Scanner pScanner, final String pLine) {
		while(true) {
			if(!pScanner.hasNextLine()) {
				throw new IllegalArgumentException();
			} else {
				final String nextLine = pScanner.nextLine();
				if(nextLine.equals(pLine)) {
					return;
				}
			}
		}
	}

	private static void verifyAsciiFileTypeOrThrow(final Scanner scanner) {
		if(!scanner.nextLine().equals(PLY_MAGIC_STRING)) {
			throw new IllegalArgumentException("Format did not match: " + PLY_MAGIC_STRING);
		} else if(!scanner.nextLine().equals(PLY_FORMAT_ASCII_10)) {
			throw new IllegalArgumentException("Format did not match: " + PLY_FORMAT_ASCII_10);
		}
	}

	private static int extractVertexCountOrThrow(final Scanner scanner) {
		final boolean matchFound = scanner.findWithinHorizon(PLY_ELEMENT_VERTEX_PATTERN, 1000) != null;
		if(matchFound) {
			final MatchResult matchResult = scanner.match();
			if(matchResult.groupCount() > 0) {
				return Integer.parseInt(matchResult.group(1));
			} else {
				throw new IllegalArgumentException();
			}
		} else {
			throw new IllegalArgumentException();
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}
